﻿using System;
using System.Collections.Generic;
using FakeItEasy;
using NUnit.Framework;

namespace Legend.IoCo.Tests
{
    [TestFixture]
    public class IoCoDefaultContainerTests
    {
        private IoCoDefaultContainer container;

        [SetUp]
        public void SetUp()
        {
            this.container = this.CreateContainer();
        }

        private IoCoDefaultContainer CreateContainer()
        {
            return new IoCoDefaultContainer();
        }

        [Test]
        public void Type_can_be_resolved_when_registered()
        {
            // Arrange
            
            // Act
            this.container.Register(new [] { typeof(IFoo) }, c => A.Fake<IFoo>());

            // Assert
            Assert.That(this.ResolveFromContainer<IFoo>(), Is.InstanceOf<IFoo>());
        }

        [Test]
        public void Type_can_be_resolved_when_other_types_has_been_registered()
        {
            // Arrange

            // Act
            this.container.Register(T<IFoo>(), c => A.Fake<IFoo>());
            this.container.Register(T<IDisposable>(), c => A.Fake<IDisposable>());

            // Assert
            Assert.That(this.ResolveFromContainer<IFoo>(), Is.InstanceOf<IFoo>());
        }

        [Test]
        public void Type_can_be_resolved_when_registered_as_more_than_one_type()
        {
            // Arrange

            // Act
            this.container.Register(new[] { typeof(IFoo), typeof(object) }, c => A.Fake<IFoo>());

            // Assert
            Assert.That(this.ResolveFromContainer<object>(), Is.Not.Null);
        }

        [Test]
        public void Creator_should_be_invoked_every_time_type_is_resolved()
        {
            // Arrange
            this.container.Register(T<IFoo>(), c => A.Fake<IFoo>());

            // Act
            var firstResolved = this.ResolveFromContainer<IFoo>();
            var secondResolved = this.ResolveFromContainer<IFoo>();
            
            // Assert
            Assert.That(firstResolved, Is.Not.SameAs(secondResolved));
        }

        [Test]
        public void TryResolve_should_return_false_when_resolved_type_has_not_been_registered()
        {
            // Arrange
            object instance = null;

            // Act
            var result = this.container.TryResolve(typeof(IFoo), out instance);

            // Assert
            Assert.That(result, Is.False);
        }

        [Test]
        public void TryResolve_should_return_true_when_resolved_type_has_been_registered()
        {
            // Arrange
            object instance = null;
            this.container.Register(T<IFoo>(), c => A.Fake<IFoo>());

            // Act
            var result = this.container.TryResolve(typeof(IFoo), out instance);

            // Assert
            Assert.That(result, Is.True);
        }

        [Test]
        public void TryResolve_should_pass_the_container_itself_into_the_creator_function()
        {
            // Arrange
            IComponentResolver passedToCreator = null;
            Func<IComponentResolver, object> creator = c =>
                {
                    passedToCreator = c;
                    return new object();
                };

            this.container.Register(T<object>(), creator);

            // Act
            this.ResolveFromContainer<object>();

            // Assert
            Assert.That(passedToCreator, Is.SameAs(this.container));
        }

        [Test]
        public void Register_should_throw_when_types_is_null()
        {
            // Arrange

            // Act

            // Assert
            Assert.That(() =>
                this.container.Register((IEnumerable<Type>)null, x => A.Fake<object>()),
                Throws.Exception.TypeOf<ArgumentNullException>());
        }

        [Test]
        public void Register_should_throw_when_creator_is_null()
        {
            // Arrange

            // Act

            // Assert
            Assert.That(() =>
                this.container.Register(T<IFoo>(), (Func<IComponentResolver, object>)null),
                Throws.Exception.TypeOf<ArgumentNullException>());
        }

        private static IEnumerable<Type> T<TType>()
        {
            return new[] { typeof(TType) };
        }

        private T ResolveFromContainer<T>()
        {
            object result = null;
            this.container.TryResolve(typeof(T), out result);
            return (T)result;
        }
    }

    public interface IFoo
    {
        void Bar();
    }
}
